/*
 *  Copyright 2005 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.webapp.util.core;

import com.blandware.atleap.webapp.struts.HeritableComponentDefinition;
import com.blandware.atleap.webapp.struts.HeritableI18nFactorySet;
import org.apache.struts.tiles.DefinitionsFactoryException;
import org.apache.struts.tiles.TilesUtil;
import org.apache.struts.tiles.definition.ComponentDefinitionsFactoryWrapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.*;

/**
 * <p>
 * Contains utilities to work with definitions.
 * </p>
 * <p><a href="DefinitionUtil.java.html"><i>View source</i></a></p>
 *
 * @author Roman Puchkovskiy <a href="mailto:roman.puchkovskiy@blandware.com">
 *         &lt;roman.puchkovskiy@blandware.com&gt;</a>
 * @version $Revision: 1.4 $ $Date: 2007/05/27 13:48:18 $
 */
public final class DefinitionUtil {
    /**
     * Commons logging instance
     */
    protected transient final Log log = LogFactory.getLog(DefinitionUtil.class);

    /**
	 * Servlet context key under which instance of this class is saved
	 */
	private static final String INSTANCE_KEY = "com.blandware.atleap.webapp.util.DefinitionUtil.INSTANCE";

	/**
	 * Creates new instance of DefinitionUtil
	 */
	private DefinitionUtil() {
	}

	/**
	 * Returns instance of DefinitionUtil corresponding to a given
     * servlet context.
	 *
     * @param servletContext Servlet context
	 * @return Instance of DefinitionUtil
	 */
	public static DefinitionUtil getInstance(ServletContext servletContext) {
		DefinitionUtil definitionUtil
                = (DefinitionUtil) servletContext.getAttribute(INSTANCE_KEY);
		if ( definitionUtil == null ) {
			definitionUtil = new DefinitionUtil();
			servletContext.setAttribute(INSTANCE_KEY, definitionUtil);
		}
		return definitionUtil;
	}

    /**
     * Returns names of all definitions. Factory is used to obtain definitions.
     *
     * @param request request
     * @return list of names
     * @throws org.apache.struts.tiles.DefinitionsFactoryException thrown if
     * error occures when obtaining definitions
     */
    public List getDefinitionsNames(HttpServletRequest request)
            throws DefinitionsFactoryException {
        return getDefinitionsNames(request, null);
    }

    /**
     * Returns names of all definitions. Definitions are given directly.
     *
     * @param definitions map of definitions
     * @return list of names
     */
    public List getDefinitionsNames(Map definitions) {
        List result = null;
        try {
            result = getDefinitionsNames(null, definitions);
        } catch (DefinitionsFactoryException e) {
            // Swallow, this can't be
        }
        return result;
    }

    /**
     * Returns names of all definitions. Two mechanisms for obtaining
     * definitions are allowed: map of them may be passed directly, or request
     * may be given to use factory.
     *
     * @param request request
     * @param definitions map of definitions; if null, factory is used
     * @return list of names
     * @throws org.apache.struts.tiles.DefinitionsFactoryException thrown if
     * error occures when obtaining definitions
     */
    protected List getDefinitionsNames(HttpServletRequest request,
                                       Map definitions)
            throws DefinitionsFactoryException {
        Map definitionsMap = definitions;
        if (definitionsMap == null) {
            HttpSession session = request.getSession();
            ServletContext servletContext = session.getServletContext();
            definitionsMap = ((HeritableI18nFactorySet) ((ComponentDefinitionsFactoryWrapper) TilesUtil.getDefinitionsFactory(request, servletContext)).getInternalFactory()).getDefinitions(request, servletContext);
        }
        List names = new ArrayList(definitionsMap.keySet());
        return names;
    }

    /**
     * Returns a list of names of definitions contained in a given set. Each
     * name is contained in list maximum one time.
     *
     * @param definitions set of definitions
     * @return list of definitions names
     */
    public List getDefinitionsNames(Set definitions) {
        Set result = new HashSet();
        Iterator i = definitions.iterator();
        while (i.hasNext()) {
            HeritableComponentDefinition definition =
                    (HeritableComponentDefinition) i.next();
            result.add(definition.getName());
        }
        return new ArrayList(result);
    }

    /**
     * Returns all definitions.
     *
     * @param request request
     * @return list of definitions
     * @throws org.apache.struts.tiles.DefinitionsFactoryException thrown if
     * error occures when obtaining definitions
     */
    public List getDefinitions(HttpServletRequest request)
            throws DefinitionsFactoryException {
        Map definitionsMap = ((HeritableI18nFactorySet) ((ComponentDefinitionsFactoryWrapper) TilesUtil.getDefinitionsFactory(request, request.getSession().getServletContext())).getInternalFactory()).getDefinitions(request, request.getSession().getServletContext());
        List definitions = new ArrayList(definitionsMap.values());
        return definitions;
    }

    /**
     * Returns names of all definitions that are descendants of a definition
     * with a given name. Definitions are obtained from factory.
     *
     * @param name name of definition for which to obtain descendants
     * @param request request
     * @param servletContext servlet context
     * @return list of descendants names
     * @throws org.apache.struts.tiles.DefinitionsFactoryException thrown if
     * error occures when obtaining definitions
     */
    public List getDescendantDefinitionsNames(String name,
                                              HttpServletRequest request,
                                              ServletContext servletContext)
            throws DefinitionsFactoryException {
        return getDescendantDefinitionsNames(name, request,
                                             servletContext, null);
    }

    /**
     * Returns names of all definitions that are descendants of a definition
     * with a given name. Definitions are passed directly.
     *
     * @param name name of definition for which to obtain descendants
     * @param definitions map of definitions to use
     * @return list of descendants names
     */
    public List getDescendantDefinitionsNames(String name, Map definitions) {
        List result = null;
        try {
            result = getDescendantDefinitionsNames(name, null, null,
                                                   definitions);
        } catch (DefinitionsFactoryException e) {
            if (log.isErrorEnabled()) {
                log.error(e);
            }
        }
        return result;
    }

    /**
     * Returns names of all definitions that are descendants of a definition
     * with a given name. Two mechanisms for obtaining definitions are
     * allowed: map of them may be passed directly, or request/servletContext
     * pair may be given.
     *
     * @param name name of definition for which to obtain descendants
     * @param request request
     * @param servletContext servlet context
     * @param definitions map of definitions to use; if null, definitions are
     * first obtained
     * @return list of descendants names
     * @throws org.apache.struts.tiles.DefinitionsFactoryException thrown if
     * error occures when obtaining definitions
     */
    protected List getDescendantDefinitionsNames(String name,
                                              HttpServletRequest request,
                                              ServletContext servletContext,
                                              Map definitions)
            throws DefinitionsFactoryException {
        if (name == null) {
            return new ArrayList();
        }

        List allDefinitions = definitions == null
                ? getDefinitionsNames(request)
                : getDefinitionsNames(definitions);
        // This set contains names of already found descendants
        Set descendants = new TreeSet();
        // This set contains names of definitions that are NOT descendants of
        // given definition
        Set notDescendants = new TreeSet();

        for (int i = 0; i < allDefinitions.size(); i++) {
            String definitionName = (String) allDefinitions.get(i);
            if (!descendants.contains(definitionName)
                    && !notDescendants.contains(definitionName)) {
                // Not already in one of sets: check if this is descendant
                // This set contains definition and its ancestors that still
                // can be descendants of parent definition
                Set definitionAndAncestors = new TreeSet();
                definitionAndAncestors.add(definitionName);
				do {
                    HeritableComponentDefinition definition;
                    if (definitions == null) {
                        definition = (HeritableComponentDefinition) TilesUtil
                                .getDefinition(definitionName, request,
                                       servletContext);
                    } else {
                        definition = (HeritableComponentDefinition) definitions
                                .get(definitionName);
                    }
                    definitionName = definition.getExtends();
                    if (definitionName != null) {
                        if (name.equals(definitionName)
                                || descendants.contains(definitionName)) {
                            // Reached parent (or its descendant), add all
                            // passed definitions as descendants
                            descendants.addAll(definitionAndAncestors);
                            break;
                        } else if (notDescendants.contains(definitionName)) {
                            // Reached a definition that is not descendant, add
                            // all passed definitions as NOT descendents
                            notDescendants.addAll(definitionAndAncestors);
                            break;
                        } else {
                            // Proceeding to parent
                            definitionAndAncestors.add(definitionName);
                        }
                    }
				} while (definitionName != null);
                // Reached root, parent wasn't met: this is NOT descendant
                notDescendants.addAll(definitionAndAncestors);
            }
        }

        return new ArrayList(descendants);
    }

    /**
     * Returns new name of definition with given parent. Name is of form
     * ${parentName}.$XXX where XXX represents three decimal digits.
     *
     * @param parentName name of parent definition
     * @param request request being processed (used to access definitions
     * factory)
     * @return unique definition name
     */
    public String generateVirtualDefinitionName(String parentName,
                                                HttpServletRequest request) {
        String prefix = parentName + ".$";
        List definitionsNames = new ArrayList();
        try {
            definitionsNames = getDefinitionsNames(request);
        } catch (DefinitionsFactoryException e) {
            if (log.isErrorEnabled()) {
                log.error(e);
            }
        }

        int number = 0;
        boolean proceed = true;
        String name = null;
        while (proceed) {
            String formatted = String.valueOf(number);
            int len = formatted.length();
            for (int i = 0; i < 3 - len; i++) {
                formatted = "0" + formatted;
            }
            name = prefix + formatted;
            proceed = definitionsNames.contains(name);
            number++;
        }

        return name;
    }

    /**
     * Determines whether first definition is ancestor of second one.
     * Definitions are given by their names.
     *
     * @param name1 name of first definition, which is checked to be ancestor
     * @param name2 name of second definition, which is checked to be descendant
     * @param definitions mapping from definition names to definitions
     * themselves
     * @return true if first definition is ancestor of second one, false if not
     * or no such definitions
     */
    public boolean isAncestor(String name1, String name2, Map definitions) {
        boolean result = false;
        HeritableComponentDefinition def1
                = (HeritableComponentDefinition) definitions.get(name1);
        if (def1 != null) {
            do {
                HeritableComponentDefinition def2
                        = (HeritableComponentDefinition) definitions.get(name2);
                if (def2 == null) {
                    break;
                }
                if (name1.equals(def2.getName())) {
                    result = true;
                    break;
                }
                name2 = def2.getExtends();
            } while (name2 != null);
        }
        return result;
    }
}
